"""

.. module:: resource
.. moduleauthor:: @author: 

The purpose of this module is ....
   
.. seealso:: modules :mod:`<module>`

*Constants*:
.. data:: constant_1
        
    constant_1 is used for ......
        
.. data:: constant_2
        
    constant_2 is used for ......
"""

# Imports
#
import logging
import types
import xml.etree.ElementTree
import xml.dom.minidom
import xml.parsers.expat
import sys
import base64

from datastore.uri import Uri
from runtime.boot import LoggingLevels


# Constants
#
CRLF = "\r\n"
    
# Classes
#

class Resource(object):
    """There are three parts to a resource: id, data and metadata. Metadata is a set
    of keys (always a full Uri) and values. The type of the value must be one of: 
    Uri, None, Boolean, Int, Long, Float, Unicode, or String. The metadata key
    doesn't need to be unique. All values are returned if needed.
         
    The data is all non class private variables in the object (these "self.__var" 
    variables are stored as "__someclass_var" in the __dict__) .
        
    The following types are recorded and faithfully recreated:
    * None, Boolean, Int, Long, Float, Unicode, Tuple, and String.
    * Dict/ResourceDict (unordered) and List.
    * The ID is stored for a Resource reference.
    * All other types are converted to string.
        
    Uri is needed. Two hard-coded namespaces are needed:
    yarra     -> http://yarra-project.com/terms/yarra/type/
    datastore -> http://yarra-project.com/terms/base/datastore/
    
    All data will be UTF-8.
    
    FIXME: will need to refactor a little to make all strings unicode.
         
    When a new Resource is created it has no id. Content and metadata are added 
    as needed. Resource ID's are always returned as unicode, not int.
         
    When the Resource is put into the DataStore for the first time, the content
    is serialised and the metadata is fetched. The id is set once it has been
    allocated. A null id is used to detect that this is a new resource.
         
    On a subsequent put, the DataStore uses the existing id, serialised the
    content, and fetches the metadata.
         
    When a Resource is fetched from the DataStore, its id, content and metadata 
    are all loaded.
        
    A resource pool can be used to recreate references between a set of resources. 
    To use this feature, a pool dictionary must be created and passed to the 
    deserialise(xml, pool) method to deserialise each resource in the graph. An 
    DeserialiseException is thrown if a new resource is added to the pool and one 
    with the same ID already exists. The key of the pool dictionary is the resource 
    id and the value is the Resource instance.
         
    Sample XML:
    
    <?xml version='1.0' encoding='UTF-8'?>
    <resource
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://yarra.googlecode.com/files/resource-1.0.xsd"
        id="001">
        <metadata>
            <item key="met">aaa</item>
            <item key="type">simple</item>
        </metadata>
        <content>
            <item key="junk" type="yarra:type/unicode">abc</item>
            <item key="b" type="yarra:type/boolean">False</item>
            <item key="morejunk" type="yarra:type/unicode">def</item>
            <item key="nill" type="yarra:type/none" />
            <list key="alist">
                <item type="yarra:type/string">one</item>
                <item type="yarra:type/string">two</item>
                <item type="yarra:string">three</item>
            </list>
            <dict key="adict">
                <item key="1" type="yarra:type/string">one</item>
                <item key="2" type="yarra:type/string">two</item>
                <list key="45">
                    <item type="yarra:type/string">four</item>
                    <item type="yarra:type/string">five</item>
                </list>
            </dict>
            <item id="001" key="ref" type="yarra:type/resource" />
        </content>
    </resource>
        
    If ResourceDict() is used instead of dict(), attribute access is available.
    For example res1.aaa.bbb is the same as resr1.aaa['bbb'] if aaa is a ResourceDict().
    When a resource is deserialised, all dicts are created as ResourceDict().
    
    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: KEY_FILENAME
        
            KEY_FILENAME is a Resource key added to each code file in the datastore.
            Its value is the name of the file.
                
        .. data:: KEY_PACKAGENAME
                
            KEY_PACKAGENAME is a Resource key added to each code file in the datastore.
            Its value is the package name of the file.
        
        .. data:: KEY_MODULENAME
                
            KEY_MODULENAME is a Resource key added to each code file in the datastore.
            Its value is <package name>.<module name> of the file.
        
        .. data:: KEY_RESOURCETYPE
                
            KEY_RESOURCETYPE is a Resource key added to each code file in the datastore.
            Its value is METADATA_MODULE.
        
        .. data:: KEY_RESOURCETYPE_VALUE_MODULE
                
            KEY_RESOURCETYPE_VALUE_MODULE is the value for the Resource key KEY_RESOURCETYPE.
    """
    
    # Class constants
    #
    
    # Configuration XML spaces namespace bindings.
    #
    BINDING_NS_NAME_ROOT = 'root'
    BINDING_NS_NAME_YARRA = 'yarra'
    BINDING_NS_NAME_DUBLINCORE = 'dc'
    BINDING_NS_NAME_DATASTORE = 'datastore' 
    BINDING_NS_NAME_PACKAGE = 'package'
    BINDING_NS_NAME_RUNTIME = 'runtime'
    BINDING_NS_NAME_CONFIG = 'config'
    BINDING_NS_NAME_TASK = 'task'
    BINDING_NS_NAME_SERVICES = 'services'
    BINDING_NS_NAME_LOCALHOST = 'localhost'
    BINDING_NS_NAME_HTMLTEMPLATE = 'htmltemplate'
    BINDING_NS_NAME_TIME = 'time'
    BINDING_NS_NAME_FILES = 'files'
    BINDING_NS_NAME_HTTPCLIENT = 'httpclient'
    BINDING_NS_NAME_SITE = 'site'
    BINDING_NS_NAME_SPEAR = 'spear'
    BINDING_NS_NAME_REFERENCE = 'reference'
    
    BINDING_NS_URI_ROOT = 'http://yarra-project.com/'
    BINDING_NS_URI_YARRA = 'http://yarra-project.com/terms/yarra/'
    BINDING_NS_URI_DUBLINCORE = 'http://purl.org/dc/elements/1.1/'
    BINDING_NS_URI_DATASTORE = 'http://yarra-project.com/terms/base/datastore/' 
    BINDING_NS_URI_PACKAGE = 'http://yarra-project.com/terms/base/package/'
    BINDING_NS_URI_RUNTIME = 'http://yarra-project.com/terms/base/runtime/'
    BINDING_NS_URI_CONFIG = 'http://localhost:8000/configuration/'
    BINDING_NS_URI_TASK = 'http://localhost:8000/task/'
    BINDING_NS_URI_SERVICES = 'http://yarra-project.com/terms/base/services/'
    BINDING_NS_URI_LOCALHOST = 'http://localhost:8000/'
    BINDING_NS_URI_HTMLTEMPLATE = 'http://localhost:8000/htmltemplate/'
    BINDING_NS_URI_TIME = 'http://localhost:8000/time/'
    BINDING_NS_URI_FILES = 'http://localhost:8000/files/'
    BINDING_NS_URI_HTTPCLIENT = 'http://localhost:8000/httpclient/'
    BINDING_NS_URI_SITE = 'http://localhost:8000/site/'
    BINDING_NS_URI_SPEAR = 'http://dpcd.vic.gov.au/terms/eplanning/spear/'
    BINDING_NS_URI_REFERENCE = 'http://localhost:8000/reference/'
    
    # Namespaces
    #
    NS_ROOT = { BINDING_NS_NAME_ROOT : BINDING_NS_URI_ROOT }
    NS_YARRA = { BINDING_NS_NAME_YARRA : BINDING_NS_URI_YARRA }
    NS_DATASTORE = { BINDING_NS_NAME_DATASTORE : BINDING_NS_URI_DATASTORE }
    NS_PACKAGE = { BINDING_NS_NAME_PACKAGE : BINDING_NS_URI_PACKAGE }
    NS_RUNTIME = { BINDING_NS_NAME_RUNTIME : BINDING_NS_URI_RUNTIME }
    NS_SERVICES = { BINDING_NS_NAME_SERVICES : BINDING_NS_URI_SERVICES }
    NS_TASK = { BINDING_NS_NAME_TASK : BINDING_NS_URI_TASK }
    NS_LOCALHOST = { BINDING_NS_NAME_LOCALHOST : BINDING_NS_URI_LOCALHOST }
    NS_DUBLINCORE = { BINDING_NS_NAME_DUBLINCORE : BINDING_NS_URI_DUBLINCORE }
    NS_REFERENCE = { BINDING_NS_NAME_REFERENCE : BINDING_NS_URI_REFERENCE }

    LOCALHOST_ROOT = BINDING_NS_URI_LOCALHOST + 'root/'
    LOCALHOST_CONFIGURATION = BINDING_NS_URI_LOCALHOST + 'configuration/'
    
    # Configuration XML key values
    #
    KEY_URI_DATASTORE_ROOT = Uri('datastore:root', NS_DATASTORE)
    KEY_URI_DATASTORE_ROOT_NODEID = Uri('datastore:root/node-id', NS_DATASTORE)
    KEY_URI_DATASTORE_ROOT_NODESTATE = Uri('datastore:root/nodestate', NS_DATASTORE)
    KEY_URI_DATASTORE_ROOT_NODELOCK = Uri('datastore:root/nodelock', NS_DATASTORE)
    KEY_URI_DATASTORE_ROOT_TIMESTAMP = Uri('datastore:root/timestamp', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_PROXY_URL = Uri('datastore:config/proxyurl', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_PROXY_USER = Uri('datastore:config/proxyuser', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_PROXY_PASSWORD = Uri('datastore:config/proxypassword', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_FIREWALL_DOWNLOADSITEURL = Uri('datastore:config/firewalldownloadsiteurl', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_FIREWALL_ADMINSITEURL = Uri('datastore:config/firewalladminsiteurl', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_DISTRIBUTION_DATASTOREURL = Uri('datastore:config/distributiondatastoreurl', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_ADMIN_SITEURL = Uri('datastore:config/adminsiteurl', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_NODE_IPADDRESS = Uri('datastore:config/nodeipaddress', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_NODE_PORTRANGEHIGH = Uri('datastore:config/nodeportrangehigh', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_NODE_PORTRANGELOW = Uri('datastore:config/nodeportrangelow', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_IS_SERVICE = Uri('datastore:config/isservice', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_USING_PROXY = Uri('datastore:config/usingproxy', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_ADMIN_PASSWORD = Uri('datastore:config/adminpassword', NS_DATASTORE)
    KEY_URI_DATASTORE_CONFIG_STATE = Uri('datastore:config/state', NS_DATASTORE)
    KEY_URI_DATASTORE_RESOURCE_TYPE = Uri('datastore:resource/type', NS_DATASTORE)
    
    KEY_URI_YARRA_VERSION = Uri('yarra:version', NS_YARRA)
    KEY_URI_YARRA_TYPE_NONE = Uri('yarra:type/none', NS_YARRA)
    KEY_URI_YARRA_TYPE_BOOLEAN = Uri('yarra:type/boolean', NS_YARRA)
    KEY_URI_YARRA_TYPE_INT = Uri('yarra:type/int', NS_YARRA)
    KEY_URI_YARRA_TYPE_LONG = Uri('yarra:type/long', NS_YARRA)
    KEY_URI_YARRA_TYPE_FLOAT = Uri('yarra:type/float', NS_YARRA)
    KEY_URI_YARRA_TYPE_UNICODE = Uri('yarra:type/unicode', NS_YARRA)
    KEY_URI_YARRA_TYPE_STRING = Uri('yarra:type/string', NS_YARRA)
    KEY_URI_YARRA_TYPE_RESOURCE = Uri('yarra:type/resource', NS_YARRA)
    KEY_URI_YARRA_TYPE_URI = Uri('yarra:type/uri', NS_YARRA)
    KEY_URI_YARRA_TYPE_BASE64 = Uri('yarra:type/base64', NS_YARRA)
    
    KEY_URI_DUBLINCORE_CONTRIBUTOR = Uri('dc:contributor', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_COVERAGE = Uri('dc:coverage', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_CREATOR = Uri('dc:creator', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_DATE = Uri('dc:date', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_DESCRIPTION = Uri('dc:description', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_FORMAT = Uri('dc:format', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_IDENTIFIER = Uri('dc:identifier', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_LANGUAGE = Uri('dc:language', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_PUBLISHER = Uri('dc:publisher', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_RELATION = Uri('dc:relation', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_RIGHTS = Uri('dc:rights', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_SOURCE = Uri('dc:source', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_SUBJECT = Uri('dc:subject', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_TITLE = Uri('dc:title', NS_DUBLINCORE)
    KEY_URI_DUBLINCORE_TYPE = Uri('dc:type', NS_DUBLINCORE)
    
    KEY_URI_PACKAGE_TYPE_PACKAGE = Uri('package:type/package', NS_PACKAGE)
    KEY_URI_PACKAGE_TYPE_CONFIGURATION = Uri('package:type/configuration', NS_PACKAGE)
    KEY_URI_PACKAGE_TYPE_SITE = Uri('package:type/site', NS_PACKAGE)
    KEY_URI_PACKAGE_TYPE_MODULE = Uri('package:type/module', NS_PACKAGE)
    KEY_URI_PACKAGE_TYPE_DATA = Uri('package:type/data', NS_PACKAGE)
    KEY_URI_PACKAGE_MANIFEST_TASKS = Uri('package:manifest/tasks', NS_PACKAGE)
    KEY_URI_PACKAGE_MANIFEST_SITES = Uri('package:manifest/sites', NS_PACKAGE)
    KEY_URI_PACKAGE_MANIFEST_BINDING = Uri('package:manifest/binding', NS_PACKAGE)
    KEY_URI_PACKAGE_MANIFEST_SPACES = Uri('package:manifest/spaces', NS_PACKAGE)
    KEY_URI_PACKAGE_FILENAME = Uri('package:filename', NS_PACKAGE)
    KEY_URI_PACKAGE_PACKAGENAME = Uri('package:packagename', NS_PACKAGE)
    KEY_URI_PACKAGE_STARTUP = Uri('package:startup', NS_PACKAGE)
    KEY_URI_PACKAGE_MODULENAME = Uri('package:modulename', NS_PACKAGE)
    KEY_URI_PACKAGE_CLASSNAME = Uri('package:classname', NS_PACKAGE)
    KEY_URI_PACKAGE_TASKURI = Uri('package:taskuri', NS_PACKAGE)
    KEY_URI_PACKAGE_DATAURI = Uri('package:datauri', NS_PACKAGE)
    KEY_URI_PACKAGE_SITEURI = Uri('package:siteuri', NS_PACKAGE)
    KEY_URI_PACKAGE_PROJECTNAME = Uri('package:projectname', NS_PACKAGE)
    KEY_URI_PACKAGE_STATUS = Uri('package:status', NS_PACKAGE)
    
    KEY_URI_RUNTIME_TYPE_FILE = Uri('runtime:/type/file', NS_RUNTIME)
    KEY_URI_RUNTIME_TASK_STATE_STARTING = Uri('runtime:task/state/starting', NS_RUNTIME)
    KEY_URI_RUNTIME_TASK_STATE_RUNNING = Uri('runtime:task/state/running', NS_RUNTIME)
    
    KEY_URI_TASK_TYPE_TASKDEF = Uri('task:type/taskdef', NS_TASK)
    
    KEY_URI_SERVICES_TYPE_HTTP_SITE = Uri('services:type/http/site', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_PORTOFFSET = Uri('services:type/http/site/portoffset', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_DEFAULT = Uri('services:type/http/site/default', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_TEMPLATEURI = Uri('services:type/http/site/templateuri', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_ACTIONS = Uri('services:type/http/site/actions', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_TRANSFORMS = Uri('services:type/http/site/transforms', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_PORT = Uri('services:type/http/site/port', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_IPADDRESS = Uri('services:type/http/site/ipaddress', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_THREAD = Uri('services:type/http/site/thread', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_HOSTNAME = Uri('services:type/http/site/hostname', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_ACTION_PARAMETERS = Uri('services:type/http/site/action/parameters', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_ACTION_ERROR = Uri('services:type/http/site/action/error', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_ACTION_RESULT = Uri('services:type/http/site/action/result', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_SITE_CODE = Uri('services:type/http/site/action/code', NS_SERVICES)
    
    KEY_URI_SERVICES_TYPE_HTTP_REQUEST = Uri('services:type/http/request', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_RESPONSE = Uri('services:type/http/response', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_TRANSFORM_TEMPLATE = Uri('services:type/transform/template', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_USER_AUTH = Uri('services:type/user/auth', NS_SERVICES)
    
    KEY_URI_SERVICES_TYPE_HTTP_REQUEST_HOST = Uri('services:type/http/request/host', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_REQUEST_COOKIE = Uri('services:type/http/request/cookie', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_REQUEST_CONTENTLENGTH = Uri('services:type/http/request/contentlength', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_REQUEST_CONTENTTYPE = Uri('services:type/http/request/contenttype', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_REQUEST_REFERER = Uri('services:type/http/request/referer', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_REQUEST_METHOD = Uri('services:type/http/request/method', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_REQUEST_URI = Uri('services:type/http/request/uri', NS_SERVICES)
    KEY_URI_SERVICES_TYPE_HTTP_REQUEST_DATA = Uri('services:type/http/request/data', NS_SERVICES)
    
    KEY_URI_REFERENCE_TYPE_APPLICATION = Uri('reference:type/application', NS_REFERENCE)
    
    # Configuration XML tag names.
    #
    TAGNAME_RESOURCE = 'resource'
    TAGNAME_METADATA = 'metadata'
    TAGNAME_ITEM = 'item'
    TAGNAME_CONTENT = 'content'
    TAGNAME_LIST = 'list'
    TAGNAME_DICT = 'dict'
    TAGNAME_TUPLE = 'tuple'
    
    # Configuration XML tag attribute names.
    #
    ATTRIBUTENAME_ID = 'id'
    ATTRIBUTENAME_KEY = 'key'
    ATTRIBUTENAME_TYPE = 'type'
    
    # Configuration XML task Uris
    #
    TASK_URI_ADMIN = Uri('task:base/service/admintask', NS_TASK)
    TASK_URI_HTTP = Uri('task:base/service/httptask', NS_TASK)
    TASK_URI_TRANSFORM = Uri('task:base/service/transformtask', NS_TASK)
    TASK_URI_TIME = Uri('task:base/service/timetask', NS_TASK)
    TASK_URI_UPDATEMANAGER = Uri('task:base/service/updatemanagertask', NS_TASK)
    TASK_URI_HTTPCLIENT = Uri('task:base/service/httpclienttask', NS_TASK)
    TASK_URI_INITIALISATION = Uri('task:base/service/initialisationtask', NS_TASK)
    
    def __init__(self, serialised_object=None, strict=False, namespaces=None):
        # _id,  _metadata, _strict and _ns are private instance variables.
        # see http://docs.python.org/3.1/tutorial/classes.html#private-variables
        #
        # Parameters:
        #     <param 1> :
        #     ...
        #
        
        # WARNING: Any instance variable set here with a name that does NOT 
        #          start with "_" will be serialised!
        #
         
        self._logger = logging.getLogger('yarra.datastore.resource.Resource')
        self._id = None
        self._metadata = ResourceDict()
        
        # strict will cause exceptions for unknown types
        self._strict = strict
        
        if namespaces is None:
            # Hard-coded namespaces.
            #
            self._ns = {
                Resource.BINDING_NS_NAME_YARRA : Resource.BINDING_NS_URI_YARRA,
                Resource.BINDING_NS_NAME_DUBLINCORE : Resource.BINDING_NS_URI_DUBLINCORE,
                Resource.BINDING_NS_NAME_DATASTORE : Resource.BINDING_NS_URI_DATASTORE,
                Resource.BINDING_NS_NAME_PACKAGE : Resource.BINDING_NS_URI_PACKAGE,
                Resource.BINDING_NS_NAME_RUNTIME : Resource.BINDING_NS_URI_RUNTIME,
                Resource.BINDING_NS_NAME_CONFIG : Resource.BINDING_NS_URI_CONFIG,
                Resource.BINDING_NS_NAME_TASK : Resource.BINDING_NS_URI_TASK,
                Resource.BINDING_NS_NAME_SERVICES : Resource.BINDING_NS_URI_SERVICES,
                Resource.BINDING_NS_NAME_LOCALHOST : Resource.BINDING_NS_URI_LOCALHOST,
                Resource.BINDING_NS_NAME_HTMLTEMPLATE : Resource.BINDING_NS_URI_HTMLTEMPLATE,
                Resource.BINDING_NS_NAME_TIME : Resource.BINDING_NS_URI_TIME,
                Resource.BINDING_NS_NAME_FILES : Resource.BINDING_NS_URI_FILES,
                Resource.BINDING_NS_NAME_HTTPCLIENT : Resource.BINDING_NS_URI_HTTPCLIENT,
                Resource.BINDING_NS_NAME_SITE : Resource.BINDING_NS_URI_SITE,
                Resource.BINDING_NS_NAME_SPEAR : Resource.BINDING_NS_URI_SPEAR
                }
        else:
            self._ns = namespaces
            
        # must be after _ns has been defined
        if serialised_object is not None:
            self.deserialise(serialised_object, None)
            
    def set_id(self, value):
        """Only set by the DataStore.
        
        :param value: <class> or <type>
        :rtype: None
        """
        
        if value is None:
            raise TypeError(
                "Resource ID cannot be None.")
        else:
            self._id = unicode(value)

    def get_id(self):
        """This function returns the resource id.
        
        :rtype: int - resource id.
        """
        
        return self._id

    def get_metadata(self, key):
        """This function returns a sorted list containing one or more values
        or an empty list.
        
        :param key: <class> or <type>
        :rtype: List
        """
        
        if self.metadata_has_key(key):
            result = self._get_metadata_values(key)
            result.sort()
            return result
        else:
            return []

    def get_single_metadata(self, key):
        """This function returns a single value for the argument metadata key,
        or an Exception if the metadata does not contain the key or there are 
        more than one value for the key.
        
        :param key: <class> or <type>
        :rtype: String
        """
        
        if self.metadata_has_key(key):
            if len(self._get_metadata_values(key)) == 1:
                return self._get_metadata_values(key)[0]
            else:
                raise IndexError("Exactly one value expected for key: %s" % key)                    
        else:
                raise IndexError("Key not found: %s" % key)                    

    def get_metadata_keys(self):
        """This function returns a sorted list of all of the unique metadata 
        keys, or none if there are no keys.
        
        :rtype: List
        """
        
        result = self._metadata.keys()
        result.sort()
        return result

    def _get_metadata_key(self, key):
        """This function returns the full uri of the key if it exists.
        
        :param key: Uri - the metadata key
        :rtype: Uri
        """
        
        try:
            full_key = key.full()
        except:
            raise TypeError('Key (%s) is not a Uri.' % key)
        found = False
        for k in self._metadata:
            if full_key == k.full():
                found = True
                break
        if found:
            return full_key
        else:
            return None
        
    def _get_metadata_values(self, key):
        """This function returns the list of values of the key if it exists.
        We don't need to test for a TypeError because we have already used
        _get_metadata_values().
        
        :param key: Uri
        :rtype: List
        """
        
        full_key = key.full()
        found = None
        for k in self._metadata:
            if full_key == k.full():
                found = self._metadata[k]
                break
        return found

    def _delete_metadata_key(self, key):
        """This function deletes the metadata key associated with the full uri 
        argument key, if it exists. We don't need to test for a TypeError 
        because we have already used _get_metadata_values().
        
        :param key: Uri
        :rtype: Boolean - True if argument metadata key is found and deleted.
        """
        
        full_key = key.full()
        found = False
        for k in self._metadata:
            if full_key == k.full():
                found = True
                del(self._metadata[k])
                break
        return found
        
    def metadata_has_key(self, key):
        """This function returns True if the argument key exists, or False otherwise.
        
        :param key: Uri
        :rtype: Boolean - True if metadata has argument key.
        """
        
        return self._get_metadata_key(key) is not None

    def get_all_metadata(self):
        """This function returns a list of all of the unique keys and values (each as a
        two-element list). The result is sorted by key then value (to aid unit testing).
        
        :rtype: List
        """
        
        all_metadata = []
        keys = self._metadata.keys()
        keys.sort()
        for key in keys:
            items = self._get_metadata_values(key)
            items.sort()
            for item in items:
                all_metadata.append([key, item])
        return all_metadata
    
    def set_metadata(self, key, value):
        """This function adds a new key/value metadata entry. 
        
        :param key: Uri
        :param value: None or one of the valid types for metadata.
        """
        
        if self.metadata_has_key(key):
            self._get_metadata_values(key).append(value)
        else:
            self._metadata[key] = [value]

    def delete_metadata(self, key, value):
        """This function deletes a key/value metadata entry. Both the key and
        value must be specified. Value may be None. This will raise an exception
        if the key or value are not found. If this was the last value for the 
        key, remove the key.
        
        :param key: Uri
        :param value: None or one of the valid types for metadata.
        """
        
        try:
            self._get_metadata_values(key).remove(value)
        except ValueError:
            raise ValueError('Metadata key (%s) not found.' % key.full_uri())
        if len(self._get_metadata_values(key)) == 0:
            self._delete_metadata_key(key)

    def get_type(self):
        """This function returns the resource type.
        
        :rtype: String
        """
        
        return self.get_single_metadata(Uri(str(Resource.KEY_URI_DATASTORE_RESOURCE_TYPE), self._ns))

    def set_type(self, value):
        """This function replaces the resource type.
        
        :param value: None or one of the valid types for metadata.
        """
        
        key = Uri(str(Resource.KEY_URI_DATASTORE_RESOURCE_TYPE), self._ns)
        if self.metadata_has_key(key):
            old_value = self.get_single_metadata(key)
            self.delete_metadata(key, old_value)
        return self.set_metadata(key, value)

    def get_vars(self):
        """This function returns those variables on the current instance of
        Resource that have names that don't begin with '_'. ie. omitting
        variables of the form "_someclass__var".

        :rtype: ResourceDict
        """
        
        vars = ResourceDict()
        for var in self.__dict__:
            if not str(var).startswith('_'):
                vars[var] = self.__dict__[var]
        return vars

    def get_var(self, key):
        """This function returns the variables of the current instance of
        Resource that has the argument name. This gives support for variables
        that don't have a valid python variable name.
        
        :param key: Variable name.
        :rtype: variable
        """
        
        return self.get_vars()[str(key)]

    def has_var(self, key):
        """This function returns True if the argument variable name 'key' exists
        in the current instance of Resource. This gives support for variables 
        that don't have a valid python variable name.
        
        :param key: Variable name
        :rtype: Boolean
        """
        
        return str(key) in self.get_vars() 

    def set_var(self, key, value):
        """This function sets a variable in the current instance of Resource. This
        gives support for variables that don't have a valid python variable name.
        
        :param key: Variable name
        :param value: None or one of the valid types for metadata.
        """
        
        self.__dict__[str(key)] = value

    def del_var(self, key):
        """This function deletes a variable in the current instance of Resource. This
        gives support for variables that don't have a valid python variable name.
        
        :param key: Variable name
        """
        
        del self.__dict__[str(key)]

    def get_var_part(self, part):
        """This function returns some part of the resource that has been
        specified as a string.
        
        If part is None or an empty string, the contents of the resource
        are returned.
        
        If part, or part of part, does not exist, then return None.
        
        :param key: Variable part.
        :rtype: variable (either simple or complex)
        """
        # Get the resources variables as a dict to start the process.
        current = self.get_vars()
        if (part is not None) and (len(str(part)) > 0):
            try:
                parts = part.split(".")
            except:
                details = "Part '%s' is not a string." % (part)
                raise GetPartException(details)
            try:
                for p in parts:
                    #print("Part: %s" % p)
                    if type(current) in [types.DictType, type(ResourceDict())]: 
                        # The dict key might be a string or an int. Try both.
                        # Other number types are not supported.
                        if p in current:
                            current = current[p]
                        else:
                            try:
                                if int(p) in current:
                                    current = current[int(p)]
                                else:
                                    # neither str(p) nor int(p) in 'current', so return None.
                                    # 
                                    return None
                            except:
                                # str(p) not in 'current' and p not an number, so return None.
                                # 
                                return None
                    else:
                        # Assume its a list-like type.
                        current = current[int(p)]
            except:
                details = "Unable to get '%s' for the Resource." % (part)
                raise GetPartException(details)
        return current

    
    def serialise(self):
        """This function returns the XML representation of the current Resource
        instance.
        
        Metadata and dicts are sorted, so a comparison of the serialised
        resource can be used to test equality.
        
        Although I would prefer to use <int>123</int>, using a generic item
        tag and a type attribute makes adding new types transparent.
        
        TODO: Add support for generic types that can be represented as a string.
        
        :rtype: String
        """

        root = self._gen_xml_header()

        if self._id is not None:
            root.attrib[Resource.ATTRIBUTENAME_ID] = self._id
        if len(self._metadata) != 0:
            meta = xml.etree.ElementTree.SubElement(root, Resource.TAGNAME_METADATA)
            for key, value in self.get_all_metadata():
                # Duplicate keys and/or values are permitted.
                # The returned elements have been sorted by get_all_metadata()
                meta_element = xml.etree.ElementTree.SubElement(meta, Resource.TAGNAME_ITEM)
                meta_element.attrib[Resource.ATTRIBUTENAME_KEY] = str(key)
                # A value of none is permitted.
                self._type_and_add(value, meta_element)        
        content = xml.etree.ElementTree.SubElement(root, Resource.TAGNAME_CONTENT)
        self._gen_xml_dict(content, self.get_vars(), None)
        return xml.etree.ElementTree.tostring(root, 'UTF-8')

    def serialise_content(self): 
        """This function returns the XML representation of the current Resource
        instance's content field.
        
        The resource ID and metadata are omitted. This method is used by the
        data store.
        
        :rtype: String
        """
        
        root = self._gen_xml_header()
        content = xml.etree.ElementTree.SubElement(root, Resource.TAGNAME_CONTENT)
        self._gen_xml_dict(content, self.get_vars(), None)
        return xml.etree.ElementTree.tostring(root, 'UTF-8')

    def _gen_xml_header(self):
        """This function returns a XML ElementTree object representing the
        Resource header.
        
        :rtype: ElementTree
        """
        
        root = xml.etree.ElementTree.Element(Resource.TAGNAME_RESOURCE)
        root.attrib['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
        root.attrib['xsi:schemaLocation'] = "http://yarra.googlecode.com/files/resource-1.0.xsd"
        return root
    
    def _gen_xml(self, et, bit, tag, key=None):
        """See http://docs.python.org/library/types.html#module-types
        
        :param et: <class> or <type>
        :param bit: <class> or <type>
        :param tag: <class> or <type>
        :param key: <class> or <type>
        """
        
        if type(bit) in [types.DictType, type(ResourceDict())]:
            self._gen_xml_dict(et, bit, Resource.TAGNAME_DICT, key)
        elif type(bit) is types.ListType:
            self._gen_xml_list_tuple(et, bit, Resource.TAGNAME_LIST, key)
        elif type(bit) is types.TupleType:
            self._gen_xml_list_tuple(et, bit, Resource.TAGNAME_TUPLE, key)
        else:
            d = xml.etree.ElementTree.SubElement(et, tag)
            self._type_and_add(bit, d, key)
            if key is not None:
                d.attrib[Resource.ATTRIBUTENAME_KEY] = str(key)

    def _type_and_add(self, bit, d, key=None):
        """This function adds a 'type' attribute to the argument 
        ElementTree.SubElement 'd', with a value that is a local Uri representing
        the type of the argument 'bit'.
        
        :param bit: <class> or <type>
        :param d: <class> or <type>
        :param key: <class> or <type>
        """
        
        if type(bit) is types.NoneType:
            d.attrib[Resource.ATTRIBUTENAME_TYPE] = str(Resource.KEY_URI_YARRA_TYPE_NONE)
        elif type(bit) is types.BooleanType:
            d.text = unicode(bit)
            d.attrib[Resource.ATTRIBUTENAME_TYPE] = str(Resource.KEY_URI_YARRA_TYPE_BOOLEAN)
        elif type(bit) is types.IntType:
            d.text = unicode(bit)
            d.attrib[Resource.ATTRIBUTENAME_TYPE] = str(Resource.KEY_URI_YARRA_TYPE_INT)
        elif type(bit) is types.LongType:
            d.text = unicode(bit)
            d.attrib[Resource.ATTRIBUTENAME_TYPE] = str(Resource.KEY_URI_YARRA_TYPE_LONG)
        elif type(bit) is types.FloatType:
            d.text = unicode(bit)
            d.attrib[Resource.ATTRIBUTENAME_TYPE] = str(Resource.KEY_URI_YARRA_TYPE_FLOAT)
        elif type(bit) is types.UnicodeType:
            d.text = unicode(bit)
            d.attrib[Resource.ATTRIBUTENAME_TYPE] = str(Resource.KEY_URI_YARRA_TYPE_UNICODE)
        elif type(bit) is types.StringType:
            d.text = str(bit)
            d.attrib[Resource.ATTRIBUTENAME_TYPE] = str(Resource.KEY_URI_YARRA_TYPE_STRING)
        elif bit.__class__.__name__ is 'Data':
            d.text = bit.encode()
            d.attrib[Resource.ATTRIBUTENAME_TYPE] = str(Resource.KEY_URI_YARRA_TYPE_BASE64)
        elif bit.__class__.__name__ is 'Uri':
            d.text = str(bit)
            d.attrib[Resource.ATTRIBUTENAME_TYPE] = str(Resource.KEY_URI_YARRA_TYPE_URI)
        elif bit.__class__.__name__ is 'Resource':
            d.attrib[Resource.ATTRIBUTENAME_TYPE] = str(Resource.KEY_URI_YARRA_TYPE_RESOURCE)
            # TODO: if there is no id on the resource, then do not add the attribute. This is because
            # if there is no id, the value will be NONE and ElementTree will throw an exception
            # during serialisation. Not sure what the behaviour should be when serialisation a 
            # parent resource with a child resource that is not persisted (i.e. no id). Need to
            # chat with Tony.
            if bit.id:
                d.attrib[Resource.ATTRIBUTENAME_ID] = bit.id
        else:
            if self._strict:
                raise DeserialisationException("Unknown type for item: %s." % str(key))
            else:
                print("Warning: unknown type for item '%s'. Setting to string." % str(key))
            d.text = str(bit)
            d.attrib[Resource.ATTRIBUTENAME_TYPE] = str(Resource.KEY_URI_YARRA_TYPE_STRING)
        
    def _gen_xml_dict(self, e, bit, tag, key=None):
        """This function ... 
        
        :param e: <class> or <type>
        :param bit: <class> or <type>
        :param tag: <class> or <type>
        :param key: <class> or <type>
        """
        
        if tag is None:
            d = e
        else:
            d = xml.etree.ElementTree.SubElement(e, tag)
            if key is not None:
                d.attrib[Resource.ATTRIBUTENAME_KEY] = str(key)
        bit_keys = bit.keys()
        bit_keys.sort()
        for b in bit_keys:
            self._gen_xml(d, bit[b], Resource.TAGNAME_ITEM, b)

    def _gen_xml_list_tuple(self, e, bit, tag, key=None):
        """This function ... 
        
        :param e: <class> or <type>
        :param bit: <class> or <type>
        :param tag: <class> or <type>
        :param key: <class> or <type>
        """
        
        d = xml.etree.ElementTree.SubElement(e, tag)
        if key is not None:
            d.attrib[Resource.ATTRIBUTENAME_KEY] = str(key)
        for b in bit:
            self._gen_xml(d, b, Resource.TAGNAME_ITEM)

    def deserialise(self, content, pool=None):
        """This function de-serialises the argument 'content'. 
        
        TODO: validate against an XML/Schema
        
        :param content: <class> or <type>
        :param pool: <class> or <type>
        """
        
        doc = self._parse_xml(content)
        self._parse_resource(doc)
        # Only add this resource to the pool if the pool exists and the resource
        # is not already in it.
        if pool is not None:
            if self.id in pool:
                raise DeserialisationException("Resource %s already exists in the pool." % self.id)
            else:
                pool[self.id] = self
        # process metadata and the actual resource content
        for part in doc.childNodes:
            if part.nodeType == part.TEXT_NODE:
                pass
            elif part.nodeType == part.COMMENT_NODE:
                pass
            elif part.tagName == Resource.TAGNAME_METADATA: 
                self._parse_metadata(part)
            elif part.tagName == Resource.TAGNAME_CONTENT: 
                self._parse_content(part, pool)
            else:
                #FIXME: No test case
                raise DeserialisationException("Unknown element under resource: %s." % part.tagName)                  

    def _parse_xml(self, content):
        """This function parses the argument 'content' XML to produce a
        DocumentElement instance.
        
        :param content: String - XML to parse.
        :rtype: DocumentElement
        """
        
        try:
            dom = xml.dom.minidom.parseString(content)
        except TypeError:
            raise DeserialisationException("Expected string or buffer.")
        except xml.parsers.expat.ExpatError:
            raise DeserialisationException("Expat parse error: %s" % sys.exc_info()[1])
        return dom.documentElement
       
    def _parse_resource(self, res):
        """This function parses the argument Resource 'res' and copies the 
        Resource's 'id' attribute in the current Resource instance.
        
        :param res: Resource - the Resource to parse for 'id'.
        """
        
        if res.tagName != Resource.TAGNAME_RESOURCE:
            raise DeserialisationException("Missing 'resource' root element.")
        if res.getAttributeNode(Resource.ATTRIBUTENAME_ID) is not None:
            self._id = res.getAttribute(Resource.ATTRIBUTENAME_ID)

    def _parse_metadata(self, metadata):
        """This function parses the argument DOM Node 'metadata' and copies all
        the contents of all 'key' fields to metadata in the current Resource instance. 
        
        :param metadata: Node
        """
        
        if metadata.hasChildNodes(): 
            for item in metadata.childNodes:
                if item.nodeType == item.TEXT_NODE:
                    continue
                if item.nodeType == item.COMMENT_NODE:
                    continue
                if item.getAttributeNode(Resource.ATTRIBUTENAME_KEY) is not None:
                    # self._parse_text(item.childNodes) might return None.
                    self.set_metadata(Uri(item.getAttribute(Resource.ATTRIBUTENAME_KEY), self._ns), self.deserialise_xml_content(item))
                else:
                    raise DeserialisationException("Missing key for metadata element.")                    

    def _parse_content(self, content, pool=None):
        """This function ... 
        
        :param param_1: <class> or <type>
        :param param_2: <class> or <type>
        :rtype: <return type>
        """
        
        if content.hasChildNodes():
            self.__dict__.update(self.deserialise_xml_content(content, pool))

    def deserialise_xml_content(self, node, pool=None):
        """This function processes the content section of a Resource. 
        
        :param node: <class> or <type>
        :param pool: int[] 
        :rtype: One of None, ResourceDict, Resource, List, tuple, Boolean, int, long, float, unicode, Uri or string.
        """
        
        yarra = Uri('yarra:', self._ns)
        if (node.tagName == Resource.TAGNAME_DICT) or (node.tagName == Resource.TAGNAME_CONTENT):
            new_dict = ResourceDict()
            for item in node.childNodes:
                if item.nodeType == item.TEXT_NODE:
                    continue
                if item.nodeType == item.COMMENT_NODE:
                    continue
                if item.getAttributeNode(Resource.ATTRIBUTENAME_KEY) is None:
                    raise DeserialisationException("Missing key for element: %s." % item.tagName)
                item_key = item.getAttribute(Resource.ATTRIBUTENAME_KEY)
                new_dict[str(item_key)] = self.deserialise_xml_content(item, pool)
            return new_dict
        elif node.tagName == Resource.TAGNAME_LIST:
            new_list = []
            for item in node.childNodes:
                if item.nodeType == item.TEXT_NODE:
                    continue
                if item.nodeType == item.COMMENT_NODE:
                    continue
                new_list.append(self.deserialise_xml_content(item, pool))
            return new_list
        elif node.tagName == Resource.TAGNAME_TUPLE:
            new_tuple = ()
            for item in node.childNodes:
                if item.nodeType == item.TEXT_NODE:
                    continue
                if item.nodeType == item.COMMENT_NODE:
                    continue
                new_tuple += self.deserialise_xml_content(item, pool),
            return new_tuple
        elif node.tagName == Resource.TAGNAME_ITEM:
            # Only items in a dict or content will have a key.
            item_key = None
            if node.getAttributeNode(Resource.ATTRIBUTENAME_KEY) is not None:
                item_key = node.getAttribute(Resource.ATTRIBUTENAME_KEY)
                
            # every item must have a type.    
            if node.getAttributeNode(Resource.ATTRIBUTENAME_TYPE) is None:
                raise DeserialisationException("Missing type for element: %s." % node.tagName)
            item_type = Uri(node.getAttribute(Resource.ATTRIBUTENAME_TYPE), self._ns)
            
            # 'none' type's value is ignored.
            if item_type == yarra.type.none:
                return None
            # 'resource' type also has no value - just an ID.
            elif item_type == yarra.type.resource:
                if pool is None:
                    raise DeserialisationException("Resource reference found but no pool specified.")
                id = node.getAttributeNode(Resource.ATTRIBUTENAME_ID)
                if id is None:
                    raise DeserialisationException("Missing id for resource element(%s)." % node.tagName)
                try:
                    res = pool[id]
                except:
                    res = Resource(namespaces=self._ns)
                    res.set_id(node.getAttribute(Resource.ATTRIBUTENAME_ID))
                    pool[id] = res
                return res
            item_text = self._parse_text(node.childNodes)
            
            # All items must have a value except string, which should result in
            # an empty string.
            #
            if item_text is None:
                if item_type == yarra.type.string:
                    item_text = ""
                else:
                    raise DeserialisationException("Element text is None for: %s (%s)." % (item_key, item_type))
            
            return self._item_type(yarra, item_type, item_text, node.tagName)
        else:
            raise DeserialisationException("Unknown element(%s)." % node.tagName)

    def _item_type(self, yarra, item_type, item_text, tag_name):
        """This function returns the argument 'item_text' but cast to the 
        internal (python) type (or Uri) that is represented by the argument
        'item_type'.
        
        :param yarra: <class> or <type>
        :param item_type: <class> or <type>
        :param item_text: <class> or <type>
        :param tag_name: <class> or <type>
        :rtype: One of Boolean, int, long, float, unicode, Uri or string.
        """
        
        if item_type == yarra.type.boolean:
            if item_text == 'True':
                return True
            elif item_text == 'False':
                return False
            else:
                raise DeserialisationException("Invalid boolean value for element(%s)." % tag_name)
        elif item_type == yarra.type.int:
            try:
                return int(item_text)
            except:
                raise DeserialisationException("Invalid int value for element(%s)." % tag_name)
        elif item_type == yarra.type.long:
            try:
                return long(item_text)
            except:
                raise DeserialisationException("Invalid long value for element(%s)." % tag_name)
        elif item_type == yarra.type.float:
            try:
                return float(item_text)
            except:
                raise DeserialisationException("Invalid float value for element(%s)." % tag_name)
        elif item_type == yarra.type.unicode:
            # XML decoding errors are detected at parse time.
            # No need to check again here.
            return unicode(item_text)
        elif item_type == yarra.type.base64:
            b = Data()
            return b.decode(item_text)
        elif item_type == yarra.type.uri:
            return Uri(item_text, self._ns)
        else:
            # this includes type string and uri
            return str(item_text)        
        
    def _parse_text(self, nodelist):
        """This function returns an string that is the concatenation of all
        XML TEXT nodes in the argument 'nodelist'. 
        
        Fetch the text children for a node. Ignore other nodes.
        Carefully distinguish between an empty text node, returned as an empty
        string and no text nodes, returned as None.

        :param nodelist: List - list of Nodes.
        :rtype: String or None
        
        These are the elements we might find here:
            Node.ELEMENT_NODE        - ignore embedded valid tags such as <br />
            Node.TEXT_NODE           - add to text
            Node.CDATA_SECTION_NODE  - add to text
            Node.ENTITY_REFERENCE_NODE            - ignore
            Node.PROCESSING_INSTRUCTION_NODE      - ignore
            Node.COMMENT_NODE                     - ignore
            Node.NOTATION_NODE                    - ignore
        """
        
        rc = None
        for node in nodelist:
            if node.nodeType == node.TEXT_NODE:
                if rc is None:
                    rc = node.data
                else:
                    rc += node.data
            elif node.nodeType == node.CDATA_SECTION_NODE:
                if rc is None:
                    rc = node.data
                else:
                    rc += node.data
            #else:
            #    print(node.nodeType, repr(node))
        return rc

    def __cmp__(self, other):
        """This function compares the current instance with the argument Resource
        instance 'other'. This should return a negative integer if self < other, 
        zero if self == other, a positive integer if self > other.
        
        This function will be called by Python for Resource comparisons, to sort
        the Resourcexs by 'id'.
        
        :param other: Resource - other Resource to compare against current instance.
        :rtype: int
        """
        
        if other is None:
            # if other is None, less just say that self is greater than other
            return 1
        elif str(self) == str(other):
            return 0
        else:
            if type(other) is type(Resource()):
                if self.id < other.id:
                    return -1
                else:
                    return 1
            else:
                return -1
        
    id = property(get_id, set_id, None, "id of resource")
#    metadata = property(get_metadata, set_metadata, None, 
#        "metadata keys and values of resource")
    
    def __str__(self):
        """This function returns a string representation of the current instance
        of Resource.
        
        :rtype: string
        """
        
        return self.serialise()
    
    def load_from_file(self, filename):
        """This function loads the current instance of Resource with the contents
        of the argument 'filename'. This function primarily supports testing, 
        especially loading uri definitions.
        
        :param filename: string
        """
        
        res_file = open(filename, 'r')
        res_xml = Resource(res_file.read())
        res_file.close()
        res_file = None
        self.deserialise(str(res_xml))
    
    def pretty_print(self):
        """This function displays the Resource in a pretty manner.
        """
        
        parser = xml.etree.ElementTree.XMLTreeBuilder(target=ResourcePrettyPrint())
        parser.feed(self.serialise())
        return parser.close()
            

class ResourceException(Exception):
    """The purpose of this class is ... 

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
    
    pass


class DeserialisationException(ResourceException):
    """The purpose of this class is ... 

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
    
    pass


class GetPartException(ResourceException):
    """The purpose of this class is ... 

    *Usage examples*::

        except GetPartException("details")

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
    
    pass


class ResourceDict(dict):
    """This dict allows attribute style access to the dictionary keys. Obviously, 
    the keys must be valid attributes names in order to use these methods. All 
    normal exceptions are raised.

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
    
    def __getattr__(self, name):
        return self.__getitem__(name)
    
    def __setattr__(self, name, value):
        return self.__setitem__(name, value)


class Data(str):
    """Allow binary data to be serialised as base64."""
    def encode(self):
        return base64.b64encode(self)
    
    def decode(self, b64):
        self = Data(base64.b64decode(b64))
        return self

class ResourcePrettyPrint:
    """This class prints the XML representation of a Resource in a pretty manner.

    *Usage examples*::

        example code

    *Common Arguments for Class methods*:
        <arguments>
    
    *Audience*:
        <audience>
    
    *Constants*:
        .. data:: constant_1
        
            constant_1 is used for ......
        
        .. data:: constant_2
        
            constant_2 is used for ......
    """
    
    # Class constants
    #
    
    def __init__(self):
        # Constructor.
        #
        # Parameters:
        #
        
        self.depth = 0
        self.str = ""
    
    def start(self, tag, attrib):
        """This function is called for each opening tag.
        """
        
        self.indent()
        
        if (len(attrib) > 0):
            self.str += "<%s " % tag
            for key in attrib.keys():
                self.str += '%s' % key
                self.str += '="%s" ' % attrib[key]
            self.str += ">"
        else:
            self.str += "<%s>" % tag
        self.str += "\n"
        
        self.depth += 1
    
    def end(self, tag):
        """This function is called for each closing tag.
        """
        
        self.depth -= 1
        
        self.indent()
        self.str += "</%s>" % tag
        self.str += "\n"
    
    def data(self, data):
        """This function displays the data.
        """
        
        self.indent()
        self.str += data.strip()
        self.str += "\n"
    
    def close(self):
        """This function is called when all of the data has been parsed.
        """
        
        return self.str

    def indent(self):
        for i in range(0, self.depth):
            self.str += "  "